package com.ypc.webdemo.service.impl;

import com.ypc.plugininterface.dto.xml.PluginDTO;
import com.ypc.webdemo.common.PluginActionEnum;
import com.ypc.webdemo.common.PluginStatusEnum;
import com.ypc.webdemo.controller.dto.PluginInDto;
import com.ypc.webdemo.dao.entity.Plugin;
import com.ypc.webdemo.dao.mapper.PluginMapper;
import com.ypc.webdemo.service.PluginService;
import com.ypc.webdemo.utils.IdUtil;
import com.ypc.webdemo.utils.MessageUtil;
import com.ypc.webdemo.utils.SpringBeanUtil;
import com.ypc.webdemo.utils.WrapperBeanCopier;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.xml.bind.JAXBContext;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Date;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author yinpengcheng
 * @Date 2023/7/21
 */
@Slf4j
@Service
public class PluginServiceImpl implements PluginService {
    private static final String PLUGIN_XML = "META-INF/plugin.xml";
    private static final String USER_DIR = System.getProperty("user.dir");
    @Value("${plugins.dir:plugins}")
    private String pluginsDir;
    @Resource
    private PluginMapper pluginMapper;

    @SneakyThrows
    @Override
    public Plugin upload(MultipartFile file) {
        Path path = Paths.get(USER_DIR, pluginsDir);
        if (Files.notExists(path) || !Files.isDirectory(path)) {
            Files.createDirectory(path);
        }
        Assert.isTrue(!file.isEmpty(), "文件不能为空");
        List<Plugin> plugins = pluginMapper.findAll(Example.of(new Plugin().setFileName(file.getOriginalFilename())));
        boolean noneMatch = plugins.stream().noneMatch(plugin -> PluginStatusEnum.installed.name().equals(plugin.getStatus()) ||
                PluginStatusEnum.uninstall_failed.name().equals(plugin.getStatus()));
        Assert.isTrue(noneMatch, "存在相同文件名的插件且已安装，如需覆盖，请卸载后重试");
        Path jarPath = Paths.get(path.toString(), file.getOriginalFilename());
        Files.copy(file.getInputStream(), jarPath, StandardCopyOption.REPLACE_EXISTING);
        PluginDTO pluginDTO = getPluginDTO(jarPath);
        Assert.notEmpty(pluginDTO.getServices(), "未获取到服务信息");
        return pluginMapper.saveAndFlush(
                new Plugin()
                        .setId(IdUtil.generateId())
                        .setKey(pluginDTO.getId())
                        .setName(pluginDTO.getName())
                        .setVersion(pluginDTO.getVersion())
                        .setDescription(pluginDTO.getDescription())
                        .setServices(pluginDTO.getServices())
                        .setChangeNotes(pluginDTO.getChangeNotes())
                        .setVendor(pluginDTO.getVendor())
                        .setFileName(file.getOriginalFilename())
                        .setCreated(new Date())
                        .setStatus(PluginStatusEnum.uninstalled.name())
        );
    }

    @Override
    public void action(String id, PluginActionEnum action) {
        Plugin plugin = pluginMapper.findById(id).orElseThrow(() -> new RuntimeException("记录不存在"));
        switch (action) {
            case install:
                if (PluginStatusEnum.installed.name().equals(plugin.getStatus())) {
                    return;
                }
                install(plugin);
                break;
            case uninstall:
                if (PluginStatusEnum.uninstalled.name().equals(plugin.getStatus())) {
                    return;
                }
                uninstall(plugin);
                break;
            default:
                throw new IllegalArgumentException("不支持的操作");
        }
    }

    @Override
    public Page<Plugin> findForPage(PluginInDto inDto) {
        return pluginMapper.findAll(
                Example.of(WrapperBeanCopier.copyProperties(inDto, Plugin.class), inDto.getExampleMatcher()),
                PageRequest.of(inDto.getPageIndex() - 1, inDto.getPageSize(), Sort.by(Sort.Direction.DESC, "created"))
        );
    }

    @Override
    public void install(Plugin plugin) {
        plugin.setMessage(null);
        Path path = Paths.get(USER_DIR, pluginsDir, plugin.getFileName());
        try (URLClassLoader classLoader = URLClassLoader.newInstance(new URL[]{new URL("file:" + path)})) {
            plugin.getServices()
                    .stream()
                    .filter(service -> Boolean.TRUE.equals(service.getEnable()))
                    .forEach(service -> {
                        try {
                            SpringBeanUtil.registerBean(classLoader.loadClass(service.getClazz()), service.getName());
                        } catch (ClassNotFoundException e) {
                            throw new RuntimeException(e);
                        }
                    });
            plugin.setStatus(PluginStatusEnum.installed.name());
        } catch (Exception e) {
            plugin.setStatus(PluginStatusEnum.install_failed.name());
            plugin.setMessage(MessageUtil.getMessage(e));
            log.error(e.getMessage(), e);
        } finally {
            pluginMapper.saveAndFlush(plugin);
        }
    }

    private PluginDTO getPluginDTO(Path path) {
        PluginDTO pluginDTO;
        try (JarFile jarFile = new JarFile(path.toFile())) {
            JarEntry entry = jarFile.getJarEntry(PLUGIN_XML);
            Assert.notNull(entry, "插件包中缺少" + PLUGIN_XML + "配置文件");
            try (InputStream is = jarFile.getInputStream(entry)) {
                pluginDTO = (PluginDTO) JAXBContext.newInstance(PluginDTO.class).createUnmarshaller().unmarshal(is);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return pluginDTO;
    }

    private void uninstall(Plugin plugin) {
        plugin.setMessage(null);
        try {
            plugin.getServices()
                    .stream()
                    .filter(service -> Boolean.TRUE.equals(service.getEnable()))
                    .forEach(service -> SpringBeanUtil.removeBean(service.getName()));
            plugin.setStatus(PluginStatusEnum.uninstalled.name());
        } catch (Exception e) {
            plugin.setStatus(PluginStatusEnum.uninstall_failed.name());
            plugin.setMessage(MessageUtil.getMessage(e));
            log.error(e.getMessage(), e);
        } finally {
            pluginMapper.saveAndFlush(plugin);
        }
    }

    @Override
    public void delete(String id) {
        pluginMapper.findById(id).ifPresent(plugin -> {
            if (PluginStatusEnum.installed.name().equals(plugin.getStatus()) || PluginStatusEnum.uninstall_failed.name().equals(plugin.getStatus())) {
                uninstall(plugin);
            }
            boolean isDelete = Paths.get(USER_DIR, pluginsDir, plugin.getFileName()).toFile().delete();
            log.info("---删除插件包文件---[{}]", isDelete ? "成功" : "失败");
            pluginMapper.deleteById(id);
        });
    }
}
